package mammuthus.master.app

import mammuthus.tool.{Constants, TemplateTool}
import java.io.File
import net.csdn.common.env.Environment
import mammuthus.template.TemplateProcessor
import akka.pattern.ask
import mammuthus.protocal._
import mammuthus.master.Master
import net.liftweb.{json => SJSon}
import mammuthus.slave.InteractiveShellCommand
import scala.collection.mutable
import net.csdn.common.collections.WowCollections
import akka.actor.ActorRef

/**
 * 5/4/15 WilliamZhu(allwefantasy@gmail.com)
 */

trait AppOperation {
  this: Master =>

  def appInstallPath = _conf.get("app.install.path", "/data/1/usr/local/applications")

  def appList = {
    _innerAppList.map(f => (f._1, f._2.hosts)).toMap
  }

  def appGetExtra(param:Map[String,String]) = {
    if (!param.getOrElse("appName","").isEmpty) slaves.filter(f => f._2._2.appInfo.filter(k => k.appName == param("appName")).size > 0).map {
      f =>
        Map("hostName" -> f._1,
          "extra" -> f._2._2.appInfo.filter(k => k.appName == param("appName"))(0).params.getOrElse(param("name"), "")
        )
    } else slaves.filter(f => f._2._2.appInfo.filter(k => k.params.getOrElse("app", "-") == param("app")).size > 0).map {
      f =>
        Map("hostName" -> f._1,
          "extra" -> f._2._2.appInfo.filter(k => k.params.getOrElse("app", "-") == param("app"))(0).params.getOrElse(param("name"), "")
        )
    }
  }

  def appPutExtra(param:Map[String,String])={
    val path = Constants.appInfoRootDir + s"${param("appName")}"

    val extraParams = Map() ++ param

    val hostNames = appInstallHosts(param("appName"))

    writeJSONFile(hostNames, extraParams, path, true)
  }

  def findIpByHost(hostName: String) = {
    if (slaves.contains(hostName)) {
      val ips = slaves(hostName)._2.ip.map(f => f.ipv4).filter(f => !f.startsWith("127.0"))
      if(ips.length>0) ips(0) else ""
    }
    else ""
  }

  def appInstallHosts(appName: String) = {
    findApp(appName) match {
    case Some(i) => i.hosts.mkString(",")
    case None => ""
    }
  }


  def appMap = {
    _innerAppList.toMap
  }

  def findApp(appName: String): Option[AppMessage] = {
    val temp = appMap
    if (temp.contains(appName)) Some(temp(appName))
    else None
  }

  def findAppInfoByAppName(appName: String): List[AppInfo] = {
    slaves.map(f => f._2._2)
      .flatMap(f => f.appInfo)
      .filter(f => f.appName == appName).toList
  }

  def findHostAndPortByAppName(appName: String,moduleName:String) = {
    findAppInfoByAppName(appName).filter {
      appInfoPerHost =>
        appInfoPerHost.params.contains("app_module_" + moduleName)
    }.map(appInfoPerHost => appInfoPerHost.params("app_module_" + moduleName))
  }

  def findSlaveInfoByAppName(appName: String): List[(String, (ActorRef, AgentInfo))] = {
    slaves.filter(f => f._2._2.appInfo.filter(f => f.appName == appName).size > 0).toList
  }

  /*
     得到 APP的相关信息，包括 hostname 列表，ip列表等
   */
  def _innerAppList = {
    slaves.map(f => f._2._2).flatMap {
      f =>
        f.appInfo.map(ai => (ai, f.hostName))
    }.groupBy(f => f._1.appName).map(f => (f._1, AppMessage(f._2.toList(0)._1, f._2.map(k => k._2).toList, f._2.map(k => findIpByHost(k._2)).toList))).toList
  }

  def appLog(hostNames: String, filePath: String, offset: Long, size: Int) = {

    _remoteExecute[AppLogResponse](hostNames, (ar, hostName) => {
      ar ? AppLogRequest(hostName, filePath, offset, size)
    })

  }

  def listAppLog(hostNames: String, filePath: String) = {

    _remoteExecute[ListAppLogResponse](hostNames, (ar, hostName) => {
      ar ? ListAppLogRequest(hostName, filePath)
    })
  }

  def gitAppInstall(hostNames: String, params: Map[String, String]) = {
    val appName = params("appName")
    val installPath = params("installPath")

    logger.info(s"gitApp install $appName  hostNames => $hostNames url => ${params("url")} ")

    val tt = if (params.contains("tt")) params("tt").toLong else InteractiveShellCommand.defaultTimeOut

    val noUserCommand = TemplateTool.render("/template/command/shell/gitClone.vm", Map(
      "installPath" -> installPath,
      "url" -> params("url"),
      "appName" -> appName
    ))

    val command = if (params.contains("user") && !WowCollections.isEmpty(params("user"))) s"mkdir -p $installPath/$appName; chown -R webuser:webuser $installPath/$appName; su - ${params("user")} /bin/bash -c '$noUserCommand'" else noUserCommand

    logger.info(s"gitApp install  $appName script  \n ${command}")
    val res = executeShellCommand(
      params("slaves"),
      command, tt
    )

    logger.info(s"gitApp install $appName execute script:\n ${SJSon.Serialization.write(res)}")

    res
  }

  def tarGzAppInstall(hostNames: String, params: Map[String, String]) = {
    val appName = params("appName")
    val app = params.getOrElse("app",appName)
    val installPath = params("installPath")
    val tt = params.getOrElse("tt",(300*1000)+"").toLong
    //进行文件下载
    //executeBlockShellCommand("rm -rf " + ${params("filePath")})
    logger.info(s"tarGzApp install $appName downloading  hostNames => $hostNames url => ${params("url")} filePath => ${params("filePath")}")
    val downloadInfo = blockDownload(hostNames, params("url"), params("filePath"))

    if (downloadInfo.filter(f => !f.success).length > 0) {
      val error = SJSon.Serialization.write(downloadInfo)
      logger.error(s"tarGzApp install $appName fail when downloading,check message:\n" + error)
      //throw new RuntimeException(error)
    }

    val fileName = params("filePath").split("/").last
    val extractFileName = fileName.replace(".tar.gz", "").replace("@","-")

    //render install.vm
    val env = new Environment(_conf)
    val installFile = new File(env.templateDirFile().getPath + s"/app/${app}/install.vm")
    val installShell = TemplateTool.render( if(installFile.exists()) s"/template/app/${app}/install.vm" else  s"/template/app/install.vm", Map(
      "installPath" -> params("installPath"),
      "sourceFileName" -> fileName,
      "extractFileName" -> extractFileName,
      "appName" -> appName,
      "installParentPath" -> installParentPath(installPath)
    ))

    logger.info(s"tarGzApp install $appName install script:\n" + installShell)

    val res = executeShellCommand(hostNames, installShell, tt)

    logger.info(s"tarGzApp install $appName execute script:\n" + SJSon.Serialization.write(res))

    res
  }

  def installApp(hostNames: String, params: Map[String, String]): mutable.ArraySeq[ExecuteShellCommandResponse] = {

    val appName = params("appName")
    val installPath = params("installPath")

    executeBlockShellCommand(hostNames, s"mkdir -p ${installParentPath(installPath)}/applications")
    executeBlockShellCommand(hostNames, s"mkdir -p ${installParentPath(installPath)}/softwares")

    val res = if (params("url").endsWith(".git")) {
      gitAppInstall(hostNames, params)
    } else {
      tarGzAppInstall(hostNames, params)
    }

    executeBlockShellCommand(hostNames, s"mkdir -p ${installParentPath(installPath)}/appinfo")

    writeFile(hostNames, "{}", s"${installParentPath(installPath)}/appinfo/${appName}")

    writeJSONFile(hostNames, Map(
      "installPath" -> params("installPath"),
      "appName" -> params("appName"),
      "version" -> params("appVersion")
    ), s"${installParentPath(installPath)}/appinfo/${appName}")

    res
  }

  def installParentPath(installPath: String) = {
    val abc = installPath.split("/")
    abc.take(abc.length - 1).mkString("/")
  }

  def configApp(hostNames: String, params: Map[String, String]):List[Map[String, Any]] = {
    case class ProcessorConfigItem(t: String, processor: String, targeFilePath: String)
    val appName = params("appName")
    val installPath = params("installPath")
    val app = params.getOrElse("app",appName)
    val mm = this
    val basePath = new Environment(_conf).templateDirFile().getPath + "/app/"

    if(!new File(basePath + s"/$app/plugin").exists()){
      //没有配置文件的app
      logger.info(basePath + s"/$app/plugin")
      return  List(Map("template" -> "", "executeResult" -> List()))

    }
    val configs = scala.io.Source.fromFile(basePath + s"/$app/plugin").
      getLines().map {
      f =>
        val Array(t, processor, targeFilePath) = f.split("\\s*->\\s*")
        (t, ProcessorConfigItem(t, processor, installPath + s"/$appName/" + targeFilePath))
    }.toMap
    logger.info(configs.mkString("\n"))
    val res = new File(basePath + "/" + app).listFiles.filterNot(f =>
      Set("install.vm", "plugin", "control.vm").contains(f.getName)
    ).map {
      f =>
        val fileName = f.getName
        val temp = fileName.split(".")
        val processor = if (configs.contains(fileName)) configs(fileName)
        else ProcessorConfigItem("-", "mammuthus.template.DefaultProcessor", installPath + "/conf/" + temp.take(temp.length - 1).mkString("."))
        logger.info(s"${processor}")
        val instance = Class.forName(processor.processor).getDeclaredConstructor(classOf[Master]).newInstance(mm).asInstanceOf[TemplateProcessor]
        (fileName, instance.process(fileName, processor.targeFilePath, params))
    }.groupBy(f => f._1).map {
      f =>
        Map("template" -> f._1, "executeResult" -> f._2.flatMap(k => k._2))
    }.toList

    writeJSONFile(hostNames, Map(
      "clientPort" -> params.getOrElse("clientPort", "-1")
    ), s"${installPath}/appinfo/${appName}")

    res
  }


}
